/*******************************************************************************
 * Copyright (c) 2007, 2011 Wind River Systems and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.dsf.mi.service.command;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;

import org.eclipse.cdt.dsf.concurrent.ConfinedToDsfExecutor;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.Query;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.mi.service.IMIBackend;
import org.eclipse.cdt.dsf.mi.service.IMIBackend.BackendStateChangedEvent;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;

/**
 * CLI Process object implementation which uses the {@link IMIBackend} service
 * to monitor and control the underlying process.
 *
 * Note that starting with GDB 7.12, as long as a PTY is available,
 * this process is no longer used.  Instead, the real GDB process,
 * along with its console will be used directly.  A second PTY
 * will be used to communicate using MI.
 * 
 * @since 1.1
 */
public class MIBackendCLIProcess extends AbstractCLIProcess {

	private IMIBackend fMIBackend;
	private AtomicInteger fExitCode = new AtomicInteger(-1); 
	private BackedExitedEventListener fExitedEventListener;
	
    @ConfinedToDsfExecutor("getSession()#getExecutor")
	public MIBackendCLIProcess(ICommandControlService commandControl, IMIBackend backend) throws IOException {
		super(commandControl);
		fMIBackend = backend;
		if (fMIBackend.getState() == IMIBackend.State.TERMINATED) {
		    fExitCode.set(fMIBackend.getExitCode());
		}
		fExitedEventListener = new BackedExitedEventListener();
        getSession().addServiceEventListener(fExitedEventListener, null);		
	}

    public class BackedExitedEventListener {
        private final List<RequestMonitor> fWaitForRMs = new ArrayList<RequestMonitor>();
        
        @DsfServiceEventHandler
        public void eventDispatched(BackendStateChangedEvent event) {
            if (event.getState() == IMIBackend.State.TERMINATED &&
            		event.getBackendId().equals(fMIBackend.getId())) 
            {
                fExitCode.set(fMIBackend.getExitCode());
                for (RequestMonitor rm : fWaitForRMs) {
                    rm.done();
                }
                fWaitForRMs.clear();
            }
        }
        
        void dispose() {
            for (RequestMonitor rm : fWaitForRMs) {
                rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.REQUEST_FAILED, "Backend terminate event never received", null)); //$NON-NLS-1$
                rm.done();
            }
            fWaitForRMs.clear();
        }
    }
    
    /**
     * @see java.lang.Process#waitFor()
     */
    @Override
    public int waitFor() throws InterruptedException {
        if (!DsfSession.isSessionActive(getSession().getId())) {
            return fExitCode.get();
        }

        try {
            Query<Object> query = new Query<Object>() {
                @Override
                protected void execute(final DataRequestMonitor<Object> rm) {
                    if ( !DsfSession.isSessionActive(getSession().getId()) || 
                         isDisposed() ||
                         fMIBackend.getState() == IMIBackend.State.TERMINATED ) 
                    {
                        rm.setData(new Object());
                        rm.done();
                    } else {
                        fExitedEventListener.fWaitForRMs.add(
                            new ImmediateRequestMonitor(rm) {
                                @Override
                                protected void handleSuccess() {
                                    rm.setData(new Object());
                                    rm.done();
                                }
                            });
                    }
                }
            };
            getSession().getExecutor().execute(query);
            query.get();
        } catch (RejectedExecutionException e) {
        } catch (ExecutionException e) {
        }
        return fExitCode.get();
    }

    
    /**
     * @see java.lang.Process#exitValue()
     */
    @Override
    public int exitValue() {
        if (!DsfSession.isSessionActive(getSession().getId())) {
            return fExitCode.get();
        }
        try {
            getSession().getExecutor().submit(new Callable<Object>() { 
            	@Override
                public Object call() throws Exception {
                    if (fMIBackend.getState() != IMIBackend.State.TERMINATED) {
                        throw new IllegalThreadStateException("Backend Process has not exited"); //$NON-NLS-1$
                    }
                    return null;
                }}).get();
        } catch (RejectedExecutionException e) {
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {
            if (e.getCause() instanceof RuntimeException) {
                throw (RuntimeException)e.getCause();
            }
        }
        return fExitCode.get();
    }
    /**
     * @see java.lang.Process#destroy()
     */
    @Override
    public void destroy() {
        try {
            getSession().getExecutor().execute(new DsfRunnable() { 	@Override public void run() {
                if (!DsfSession.isSessionActive(getSession().getId())) return;
                if (isDisposed()) return;

                fMIBackend.destroy();
            }});
        } catch (RejectedExecutionException e) {
            // Session disposed.
        }
    }
    
    @Override
    public void dispose() {
        fExitedEventListener.dispose();
        getSession().removeServiceEventListener(fExitedEventListener);        
        super.dispose();
    }
}
